#include <array>
#include <chrono>
#include <cstring>
#include <iostream>
#include <span>
#include <thread>
#include <vector>

#include "ani_helpers_build_scripts.h"
#include <ani.h>

static ani_object impl_AnyInOut([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class cls, ani_object val) {
  return val;
}

static ani_object impl_ObjInOut([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class cls, ani_object val) {
  return val;
}

static ani_long impl_Int2Long(ani_int a) { return a + 1; }

static ani_int impl_Sum(ani_int a, ani_int b) {
  // std::this_thread::sleep_for(std::chrono::milliseconds(10));
  return a + b;
}

static ani_int impl_SumInstance([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object, ani_int a,
                                ani_int b) {
  return a + b;
}

static void impl_checkBuf([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class cls, ani_arraybuffer arBuf) {
  void *data{};
  size_t len{};
  CHECK_ANI_FATAL(env->ArrayBuffer_GetInfo(arBuf, &data, &len));

  std::cout << "native check len: " << len << std::endl;
  std::cout << "native check data: " << data << std::endl;

  std::span<uint8_t> buf(static_cast<uint8_t *>(data), len);
  for (uint8_t val : buf) {
    std::cout << "native check byte: " << static_cast<unsigned int>(val) << std::endl;
    ;
  }
}

static ani_arraybuffer impl_getBuf([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_class cls, ani_int len) {
  std::vector<uint8_t> nativeBuf;
  for (int i = 0; i < len; ++i) {
    nativeBuf.push_back((i + 1) * 10 + 1);
  }

  void *data{};
  ani_arraybuffer res;
  CHECK_ANI_FATAL(env->CreateArrayBuffer(len, &data, &res));
  std::memcpy(data, nativeBuf.data(), len);
  return res;
}

ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) {
  ani_env *env;
  CHECK_ANI_FATAL(vm->GetEnv(ANI_VERSION_1, &env));

  static const char *className = "main.Calc";
  ani_class cls;
  CHECK_ANI_FATAL(env->FindClass(className, &cls));

  std::array methods = {
      ani_native_function{"sumInstance", "ii:i", reinterpret_cast<void *>(impl_SumInstance)},
  };
  CHECK_ANI_FATAL(env->Class_BindNativeMethods(cls, methods.data(), methods.size()));

  std::array staticMethods = {
      ani_native_function{"sum", "ii:i", reinterpret_cast<void *>(impl_Sum)},
      ani_native_function{"getBuf", "i:C{std.core.ArrayBuffer}", reinterpret_cast<void *>(impl_getBuf)},
      ani_native_function{"checkBuf", "C{std.core.ArrayBuffer}:", reinterpret_cast<void *>(impl_checkBuf)},
      ani_native_function{"Int2Long", "i:l", reinterpret_cast<void *>(impl_Int2Long)},
      ani_native_function{"ObjInOut", "Y:Y", reinterpret_cast<void *>(impl_ObjInOut)},
      ani_native_function{"AnyInOut", "Y:Y", reinterpret_cast<void *>(impl_AnyInOut)},
  };

  CHECK_ANI_FATAL(env->Class_BindStaticNativeMethods(cls, staticMethods.data(), staticMethods.size()));

  *result = ANI_VERSION_1;
  return ANI_OK;
}
